import { ReactiveProperty } from './ReactiveProperty';
import { rxccconfig } from './RxCCConfig';
import { Observable, EMPTY, map } from 'rxjs';
import { UITransform, Button, isValid, warn, Node, v3, math, KeyCode, EventTouch, v2, Vec3, Camera, Vec2 } from "cc";
import { linear } from '../../utils/math';

const _v20 = v2();
const _v21 = v2();

const _v30 = v3();
const _v31 = v3();

export function BindNodeTouchStart(node: Node): Observable<EventTouch> {
    if (!node) {
        warn('BindNodeTouchStart, node is null'); return EMPTY;
    }
    return new Observable(observer => {
        const onTouchStart = param => observer.next(param);
        node.on(Node.EventType.TOUCH_START, onTouchStart);
        return () => {
            if (isValid(node)) {
                node.off(Node.EventType.TOUCH_START, onTouchStart);
            }
        }
    }).pipe(map((_: EventTouch) => {
        return _;
    }));
}

export function BindNodeTouch(node: Node): Observable<EventTouch> {
    if (!node) {
        warn('BindNodeTouch, node is null'); return EMPTY;
    }
    return new Observable(observer => {
        const onTouchStart = param => observer.next(param);
        const onTouchMove = param => observer.next(param);
        const onTouchEnd = param => observer.next(param);
        const onTouchCancel = param => observer.next(param);
        node.on(Node.EventType.TOUCH_START, onTouchStart);
        node.on(Node.EventType.TOUCH_MOVE, onTouchMove);
        node.on(Node.EventType.TOUCH_END, onTouchEnd);
        node.on(Node.EventType.TOUCH_CANCEL, onTouchCancel);
        return () => {
            if (isValid(node)) {
                node.off(Node.EventType.TOUCH_START, onTouchStart);
                node.off(Node.EventType.TOUCH_MOVE, onTouchMove);
                node.off(Node.EventType.TOUCH_END, onTouchEnd);
                node.off(Node.EventType.TOUCH_CANCEL, onTouchCancel);
            }
        }
    }).pipe(map((_: EventTouch) => {
        return _;
    }));
}

/**
 * 这是一个在Canvas视图内进行节点拖动的Rx封装，在已经实现了拖动的同时，可以观察到touch-start/move/cancel/end的行为，以及touch-end-click点击行为
 * @param node 要拖动的节点
 * @param camera 观察这个节点的摄像机 （由于touch给定的是屏幕坐标，需要通过摄像机的方法转为世界坐标来进行节点的移动）
 * @param prediceFunc 动态使能性判定
 * @returns 
 */
export function BindDragAndClickNode(
    node: Node,
    camera: Camera,
    prediceFunc?: (param: { type: string, eventTouch: EventTouch, worldPosition: Vec3 }) => boolean
): Observable<{ type: string, eventTouch: EventTouch, worldPosition: Vec3 }> {
    const _diff = v3();
    return BindNodeTouch(node).pipe(map((eventTouch: EventTouch) => {
        // this.log('@eventTouch', eventTouch);
        eventTouch.getLocation(_v20);
        eventTouch.getStartLocation(_v21);
        touchLocationToWorldPosition(camera, _v20, _v30);
        let enabled = true;
        if (prediceFunc != null) enabled = prediceFunc({ type: eventTouch.type, eventTouch, worldPosition: _v30 });
        if (enabled) {
            switch (eventTouch.type) {
                case 'touch-start':
                    node.getWorldPosition(_diff);
                    _diff.subtract(_v30);
                    break;
                case 'touch-move':
                    _v30.add(_diff);
                    node.setWorldPosition(_v30);
                    break;
                case 'touch-cancel':
                    break;
                case 'touch-end':
                    // 计算点击
                    if (_v20.subtract(_v21).length() < 10) {
                        return { type: 'touch-end-click', eventTouch, worldPosition: _v30 };
                    }
                    break;
            }
        }
        return { type: eventTouch.type, eventTouch, worldPosition: _v30 };
    }));
}

/**
 * 与BindDragAndClickNode相似，但限制移动范围
 * @param node 
 * @param camera 
 * @param limit (本地坐标最小值和最大值)
 */
export function BindDragWithLimited(node: Node, camera: Camera, limit: { x: number[], y: number[] })
    : Observable<{ type: string, eventTouch: EventTouch, worldPosition: Vec3, xRate: number, yRate: number }> {
    const _diff = v3();
    let xRate = .5;
    let yRate = .5;
    return BindNodeTouch(node).pipe(map((eventTouch: EventTouch) => {
        eventTouch.getLocation(_v20);
        eventTouch.getStartLocation(_v21);
        touchLocationToWorldPosition(camera, _v20, _v30);
        node.parent.getComponent(UITransform).convertToNodeSpaceAR(_v30, _v31);
        xRate = linear(limit.x[0], limit.x[1], 0, 1, _v31.x);
        yRate = linear(limit.y[0], limit.y[1], 0, 1, _v31.y);
        switch (eventTouch.type) {
            case 'touch-start':
                node.getWorldPosition(_diff);
                _diff.subtract(_v30);
                break;
            case 'touch-move':
                _v30.add(_diff);
                if (_v31.x < limit.x[0]) _v31.x = limit.x[0];
                if (_v31.x > limit.x[1]) _v31.x = limit.x[1];
                if (_v31.y < limit.y[0]) _v31.y = limit.y[0];
                if (_v31.y > limit.y[1]) _v31.y = limit.y[1];
                node.parent.getComponent(UITransform).convertToWorldSpaceAR(_v31, _v30);
                node.setWorldPosition(_v30);
                break;
            case 'touch-cancel':
                break;
            case 'touch-end':
                // 计算点击
                if (_v20.subtract(_v21).length() < 10) {
                    return { type: 'touch-end-click', eventTouch, worldPosition: _v30, xRate, yRate };
                }
                break;
        }
        xRate = linear(limit.x[0], limit.x[1], 0, 1, _v31.x);
        yRate = linear(limit.y[0], limit.y[1], 0, 1, _v31.y);
        return { type: eventTouch.type, eventTouch, worldPosition: _v30, xRate, yRate };
    }));
}

export function touchLocationToWorldPosition(camera: Camera, location: Vec2, _out?: Vec3) {
    const result = camera.screenToWorld(v3(location.x, location.y, 0), _out ?? v3());
    _out.z = 0;
    result.z = 0;
    return result;
}

export function BindNodeTouchStartEnd(node: Node, reactiveProperty?: ReactiveProperty<boolean>): Observable<TouchEvent> {
    if (!node) { warn('BindNodeTouchStartEnd, node is null'); return EMPTY; }
    return new Observable(observer => {
        const onTouchStart = param => {
            rxccconfig.buttonWillClickCommand.execute({ node: node });
            observer.next(param);
        }
        const onTouchEnd = param => observer.next(param);
        const onTouchCancel = param => observer.next(param);
        node.on(Node.EventType.TOUCH_START, onTouchStart);
        node.on(Node.EventType.TOUCH_END, onTouchEnd);
        node.on(Node.EventType.TOUCH_CANCEL, onTouchCancel);
        return () => {
            if (isValid(node)) {
                node.off(Node.EventType.TOUCH_START, onTouchStart);
                node.off(Node.EventType.TOUCH_END, onTouchEnd);
                node.off(Node.EventType.TOUCH_CANCEL, onTouchCancel);
            }
        }
    }).pipe(map((_: TouchEvent) => {
        if (reactiveProperty != null) reactiveProperty.value = _.type == Node.EventType.TOUCH_START;
        return _;
    }));
}

declare module "cc" {
    interface Node {
        setX: (x: number) => void
        setY: (y: number) => void
        setZ: (z: number) => void
    }
}

let __pos: math.Vec3 = v3();
Node.prototype.setX = function (x: number) {
    this.getPosition(__pos);
    __pos.x = x;
    this.setPosition(__pos);
}

Node.prototype.setY = function (y: number) {
    this.getPosition(__pos);
    __pos.y = y;
    this.setPosition(__pos);
}

Node.prototype.setZ = function (z: number) {
    this.getPosition(__pos);
    __pos.z = z;
    this.setPosition(__pos);
}